import { _decorator, Collider2D, Color, Component, ERaycast2DType, EventTouch, Graphics, instantiate, Intersection2D, log, Node, PhysicsSystem2D, PolygonCollider2D, v2, Vec2, warn } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('CutMultiple')
export class CutMultiple extends Component {
    // 用来切割的本体节点
    @property(Node)
    baseNodes: Node = null!;
    // 画线工具(模拟小刀)
    @property(Graphics)
    cutGraphics: Graphics;

    // 根据射线检测的碰撞体分类交点
    _colliderList: {collider: PolygonCollider2D, joints: Vec2[]}[] = [];

    onLoad () {
        this.baseNodes.children.forEach(node=>{
            // 绘制用来切割的本体
            this.drawCollider(node);
        })
        // 注册监听触摸事件
        this.registerEvent();
    }

    /**
     * 根据物理包围盒的 points 填充绘图
     * @param box 注意要使用多边形碰撞盒
     * @param isNewColor 是否随机颜色
     */
    private drawCollider (box: Node, isNewColor: boolean = false) {
        const points = box.getComponent(PolygonCollider2D).points;
        const ctx = box.getComponent(Graphics);
        ctx.clear();
        const len = points.length;
        ctx.moveTo(points[len - 1].x, points[len - 1].y);
        for (let i = 0; i < points.length; i++) {
            ctx.lineTo(points[i].x, points[i].y);
        }
        if (isNewColor) {
            //随机颜色
            ctx.fillColor = new Color(Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255));
        }
        ctx.fill();
    }

    /**
     * 注册监听触摸事件
     */
    private registerEvent () {
        this.node.on(Node.EventType.TOUCH_MOVE, (e: EventTouch) => {
            this.cutGraphics.clear();
            const startPoint = e.getUIStartLocation();
            const movePoint = e.getUILocation();
            this.cutGraphics.moveTo(startPoint.x, startPoint.y);
            this.cutGraphics.lineTo(movePoint.x, movePoint.y);
            this.cutGraphics.stroke();
        }, this);
        this.node.on(Node.EventType.TOUCH_END, (e: EventTouch) => {
            this.cutGraphics.clear();
            const startPoint = e.getUIStartLocation();
            const endPoint = e.getUILocation();
            log(`画线起始点：${startPoint}，画线终点：${endPoint}`);
            // 检测两个画线点与碰撞体相交
            this.checkPoints(startPoint, endPoint);
        }, this);
    }

    /**
     * 检测两个画线点与碰撞体相交
     * 将交点按碰撞体进行分类
     */
    private checkPoints(point1, point2) {
        // 射线检测 （All：检测射线路径上的所有碰撞体）
        const result1 = PhysicsSystem2D.instance.raycast(point1, point2, ERaycast2DType.All);
        // 获取所有的交点，需要正反来两次射线检测
        const result2 = PhysicsSystem2D.instance.raycast(point2, point1, ERaycast2DType.All);
        // 将结果合并
        let results = result1.concat(result2);
        // 根据结果统计碰撞体
        this._colliderList = [];
        for (let i = 0; i < results.length; i++) {
            // 判断多边形物理碰撞盒
            if (results[i].collider instanceof PolygonCollider2D) {
                // 是否已经存在
                let repeatFlag = false;
                if (this._colliderList.length > 0) {
                    for (let j = 0; j < this._colliderList.length; j++) {
                        if (results[i].collider === this._colliderList[j].collider) {
                            repeatFlag = true;
                        }
                    }
                }
                // 相同的Collider不重复装入
                if (!repeatFlag) {
                    this._colliderList.push({
                        collider: <PolygonCollider2D>results[i].collider, 
                        joints: []
                    })
                }
            }
        }
        log("待切割碰撞体个数：", this._colliderList.length);
        // 将交点按碰撞体进行分类
        for (let i = 0; i < this._colliderList.length; i++) {
            let baseJoints: Vec2[] = [];
            for (let j = 0; j < results.length; j++) {
                if (this._colliderList[i].collider === results[j].collider) {
                    let repeatPoint = baseJoints.find(item => {
                        // 物理世界没有绝对相等，官方取的判断临界是根号 5，很小的距离来判断点在同一位置
                        // 注意用clone()来计算，不然会改变原来的值
                        return (item.clone().subtract(results[j].point.clone()).lengthSqr() <= 5);
                    });
                    // 移除同碰撞体内部的多余的点，成对位置相等（很近）
                    if (repeatPoint) {
                        baseJoints.splice(baseJoints.indexOf(repeatPoint), 1);
                    } else {
                        baseJoints.push(results[j].point);
                    }
                }
            }
            // 把每个碰撞体的点分别处理
            this._colliderList[i].joints = baseJoints.sort();
            log(`碰撞体${i}的交点：${this._colliderList[i].joints}`);
        }
        // 根据分类处理切割
        this.cutCollider();
    }

    /**
     * 根据按碰撞体进行分类的交点处理切割
     */
    private cutCollider() {
        // 遍历已经分好的碰撞体及其交点
        this._colliderList.forEach((item, idx) => {
            // 至少两个点才能切割
            if (item.joints.length >= 2) {
                // 将一个碰撞体上的所有点分成几个部分，比如两个交点就是两部分，四个交点就可能需要分成三部分
                let splitResults: { points: Vec2[], edge: number[] }[] = [];
                for (let i = 0; i < item.joints.length; i+=2) {
                    // 每两点循环一次
                    if (item.joints[i] && item.joints[i + 1]) {
                        // 将分割后的结果放入 splitResults 中
                        let jointPoint1: Vec2 = v2(0, 0);
                        let jointPoint2: Vec2 = v2(0, 0);
                        // 射线与碰撞体相交的点 转换为 刚体本地坐标系下的点
                        item.collider.body.getLocalPoint(item.joints[i], jointPoint1);
                        item.collider.body.getLocalPoint(item.joints[i + 1], jointPoint2);
                        log(`碰撞体${idx}：交点${i}：${jointPoint1}，交点${i+1}：${jointPoint2}`);
                        // 把碰撞体的顶点以两个交点为界限分割
                        this.splitPoints(item.collider.points, jointPoint1, jointPoint2, splitResults);
                    }
                }
                log("待重新绘制个数：", splitResults.length);
                log("待重新绘制的点：", JSON.stringify(splitResults));
                // 重新设置碰撞体顶点（真正执行切割）
                if (splitResults.length > 0) {
                    // 设置本体碰撞体顶点
                    item.collider.points = splitResults[0].points;
                    item.collider.apply();
                    // 重新绘制本体
                    this.drawCollider(item.collider.node);
                    // 克隆 N 个
                    for (let j = 1; j < splitResults.length; j++) {
                        let cutPoints = splitResults[j].points;
                        if (cutPoints.length < 3) continue;
                        // 克隆本体作为第 N 个
                        const cloneNode = instantiate(item.collider.node);
                        this.node.addChild(cloneNode);
                        const cutCollider = cloneNode.getComponent(PolygonCollider2D);
                        cutCollider.points = cutPoints;
                        cutCollider.apply();
                        // 绘制第二个，随机颜色
                        this.drawCollider(cloneNode, true);
                    }
                }
            }
        });
    }

    /**
     * 把碰撞体的顶点以每两个交点为界限分割
     * @param baseCollider 碰撞体
     * @param jointPoint1 射线与碰撞体相交点1 
     * @param jointPoint2 射线与碰撞体相交点2
     */
    private splitPoints(points: Vec2[], jointPoint1: Vec2, jointPoint2: Vec2, splitResults) {
        // 多边形顶点数组
        log("原始顶点数组：", JSON.stringify(points));
        // 获取相交点在碰撞体边的下标
        let jointEdge1 = undefined;
        let jointEdge2 = undefined;
        for (let i = 0; i < points.length; i++) {
            let p1 = points[i];
            let p2 = i === points.length - 1 ? points[0] : points[i + 1];
            // 计算点到直线的距离（近似判断点在线上,记录交点边的下标） pointLineDistance(point,lineStart,lineEnd,isSegment)
            if (Intersection2D.pointLineDistance(jointPoint1, p1, p2, true) < 1) {
                jointEdge1 = i;
            }
            if (Intersection2D.pointLineDistance(jointPoint2, p1, p2, true) < 1) {
                jointEdge2 = i;
            }
            if (jointEdge1 !== undefined && jointEdge2 !== undefined) {
                break;
            }
        }
        log(`相交点1的边下标${jointEdge1}，交点2的边下标${jointEdge2}`);
        // 定义此次待切割的所有点
        let splitPoints: { points: Vec2[], edge: number[] } = { points: [], edge: [] };
        // 此次分割的位置
        let index1 = jointEdge1;
        let index2 = jointEdge2;
        // 检测重叠部分
        let repeatFlag = false;
        if (splitResults.length > 0) {
            for (let i = 0; i < splitResults.length; i++) {
                // 检测重叠：同一碰撞体之前分割点对应边的下标 是否同时包含此次分割两个交点边的下标
                let i1 = splitResults[i].edge.indexOf(jointEdge1);
                let i2 = splitResults[i].edge.indexOf(jointEdge2);
                if (i1 !== -1 && i2 !== -1) {
                    repeatFlag = true;
                    splitPoints = splitResults.splice(i, 1)[0];
                    // 更新此次分割的位置
                    index1 = i1;
                    index2 = i2;
                    break;
                }
            }
        }
        // 如果没有重叠，将碰撞体所有点装入
        if (!repeatFlag) {
            splitPoints = { points: [], edge: [] };
            for (let i = 0; i < points.length; i++) {
                // 顶点
                splitPoints.points.push(points[i].clone());
                // 对应边的下标
                splitPoints.edge.push(i);
            }
        }
        log("将被分割的点：", splitPoints);
        log(`分割位置1：${index1}，分割位置2：${index2}`);
        // 分割后的第一部分
        let cutPoints1: { points: Vec2[], edge: number[] } = { points: [], edge: [] };
        cutPoints1.points.push(jointPoint2);
        cutPoints1.edge.push(jointEdge2);
        cutPoints1.points.push(jointPoint1);
        cutPoints1.edge.push(jointEdge1);
        // 从位置1走到位置2，装载路径上的所有点
        for (let i = (index1 + 1); i !== (index2 + 1); i++) {
            if (i >= splitPoints.points.length) {
                i = 0;
            }
            cutPoints1.points.push(splitPoints.points[i]);
            cutPoints1.edge.push(splitPoints.edge[i]);
        }
        splitResults.push(cutPoints1);
        log("分割后的第一部分：", cutPoints1);
        // 分割后的第二部分
        let cutPoints2: { points: Vec2[], edge: number[] } = { points: [], edge: [] };
        cutPoints2.points.push(jointPoint1);
        cutPoints2.edge.push(jointEdge1);
        cutPoints2.points.push(jointPoint2);
        cutPoints2.edge.push(jointEdge2);
        // 再从位置2走到位置1，装载路径上的所有点
        for (let i = (index2 + 1); i !== (index1 + 1); i++) {
            if (i >= splitPoints.points.length) {
                i = 0;
            }
            cutPoints2.points.push(splitPoints.points[i]);
            cutPoints2.edge.push(splitPoints.edge[i]);
        }
        splitResults.push(cutPoints2);
        log("分割后的第二部分：", cutPoints2);
    }
}
